package com.cxx.purchasecharge.core.config;

import java.io.IOException;
import java.util.Map;
import java.util.Properties;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;
import org.springframework.core.io.Resource;

/**
 * This class extends the Spring property place holder configurer to implement functionality
 * to support encrypted property values and also value substitution.  For example if the parameters
 * value contains ${name}, it will try and look for a parameter called "name" and use its value for the
 * parameter.  This lookup is only supported one-level deep, it does not support nested values.  Also,
 * if the value starts with the "!encrypted!" keyword the rest of the value is considered to be an 
 * encrypted value, there cannot be any additional prefixes "${name}" or more than one "!encrypted!" in a 
 * value that starts with "!encrypted!".
 */
public class CxxPropertiesPlaceholderConfigurer extends PropertyPlaceholderConfigurer
{
    private static final Logger logger = Logger.getLogger(CxxPropertiesPlaceholderConfigurer.class);
    
    // The following keys are for value substitution
    // if you have the following
    // default.parameter=1234
    // myparameter=${default.parameter}
    // The actual value for myparameter would be 1234
    private static final String PREFIX_KEY = "${";
    private static final String SUFFIX_KEY = "}";
    
    private String[] groupNames;
    
    /**
     * Constructor, creates an instance of the crypto factory implementation.
     */
    public CxxPropertiesPlaceholderConfigurer()
    {
    }
    
    
    /* (non-Javadoc)
     * @see org.springframework.beans.factory.config.PropertyPlaceholderConfigurer#resolvePlaceholder(java.lang.String, java.util.Properties)
     */
    @Override
    protected String resolvePlaceholder(String placeholder, Properties props)
    {
        String value = props.getProperty (placeholder);
        if ( value != null )
        {   
            String[] replacements = StringUtils.substringsBetween (value, PREFIX_KEY, SUFFIX_KEY);
            
            if ( replacements != null && replacements.length > 0)
            {
                for ( String substitute : replacements)
                {
                    String newValue = props.getProperty (substitute);
                    if ( newValue != null )
                    {                        
                        // Replace the value if the new value was found or it was decrypted, otherwise
                        // just use the original value.
                        value = StringUtils.replace (value,PREFIX_KEY + substitute + SUFFIX_KEY, newValue);
                    }
                }
            }
        }
        
        return value;
    }

    /**
     * Sets the group name or property file name.
     * @param group
     */
    public void setGroupName (String group)
    {
        groupNames = new String[] {group};
    }
    
    /**
     * Set the groups names which are the property file names.
     * @param groups
     */
    public void setGroupNames (String[] groups)
    {
        this.groupNames = groups;
    }
    
    @Override
    protected void loadProperties(Properties props) throws IOException 
    {
        if (groupNames != null)
        {
            // we load each property file individually, even though CfgPropertiesFactory could load them all at once
            for (String group : groupNames)
            {
                try
                {
                    CxxProperties cfg = CxxPropertiesFactory.getProperties(group);
                    
                    for (Map.Entry<Object,Object> entry : cfg.entrySet())
                    {
                        // values are already decrypted
                        props.setProperty (entry.getKey().toString(),entry.getValue().toString());
                    }
                }
                catch (ConfigException e)
                {
                    logger.warn("Exception loading properties for group(s): " + groupNames, e);
                }
            }
        }
    }
    
    /**
     * This is used for testing purposes.
     * @param placeholder
     * @param props
     * @return the property value
     */
    public String getProperty(String placeholder, Properties props)
    {
        return this.resolvePlaceholder (placeholder, props);
    }
    
    
    /* (non-Javadoc)
     * @see org.springframework.core.io.support.PropertiesLoaderSupport#setLocation(org.springframework.core.io.Resource)
     */
    @Override
    public void setLocation (Resource location)
    {
        throw new UnsupportedOperationException("Must specify groupName(s) instead of location");
    }



    /* (non-Javadoc)
     * @see org.springframework.core.io.support.PropertiesLoaderSupport#setLocations(org.springframework.core.io.Resource[])
     */
    @Override
    public void setLocations (Resource[] locations)
    {
        throw new UnsupportedOperationException("Must specify groupName(s) instead of location");
    }
}
